///////////////////////////////////////////////////////////////////////////////
//	FILENAME:	eTComponent.m
//	SUMMARY:	Implementation of a generic external-data-container class.
//	SUPERCLASS:	Object
//	INTERFACE:	None
//	PROTOCOLS:	<ComponentData,
//				ETFDSupport,ASCIISupport,HTMDSupport, LaTeXSupport>
//	AUTHOR:		Rohit Khare and Tom Zavisca
//	COPYRIGHT:	(c) 1994 California Institure of Technology, eText Project
///////////////////////////////////////////////////////////////////////////////
//	IMPLEMENTATION COMMENTS
//		This implementation is a hack: it uses system() calls to have files
//	copied. This avoids setting up our own buffer functions and works
//	for directories, too. This also just a default implementation; subclasses
//	will most likely define their own methods.
//
//	The revision of 7/19 introduces a new two-phase lifecycle, 
//	initInDoc:linked: followed by readComponent from path or Pboard.
//	
//	Eventually, there is room for an eTComponentBinder that will uniq' refs.
///////////////////////////////////////////////////////////////////////////////
//	HISTORY
//	12/23/94:	Patched to reset eTDoc everywhere
//	08/06/94:	Added symbolic linking support.
//	07/19/94:	Rewritten/Reorganized as described in Actors/eTComponent.rtf
//	07/10/94:	Created.
///////////////////////////////////////////////////////////////////////////////

#import "eTComponent.h"

#define _eTComponentVERSION	20

@implementation eTComponent
//	NXAtom	componentName;
//	NXAtom	currentPath;
//	BOOL	shouldEdit;
//	BOOL	isDirty;
//	BOOl	isLinked;
//	id		etDoc;
//	id		icon;

//////////// ACCESSOR METHODS ////////////////
- setComponentName:(const char*)newComponentName
	{componentName = NXUniqueString(newComponentName); return self;}
- (const char *) componentName
	{return componentName;}
- (const char *) currentPath
	{return currentPath;}
- etDoc
	{return etDoc;}
- icon
{
	if (!icon) {
		icon = [[Application workspace] getIconForFile:currentPath];
		if (!icon) icon = [NXImage findImageNamed:"NXdefaulticon"];
	}
	return icon;}
- (BOOL) isLinked
	{return isLinked;}
- (BOOL) isMutable		// this call checks shouldEdit
	{return (shouldEdit && (!isLinked));}
- setShouldEdit:(BOOL) newState
	{shouldEdit = newState; return self;}
- setLinked:(BOOL) newState
	{isLinked = newState; return self;}
- setDoc:newDoc {
	if (newDoc && [newDoc respondsTo:@selector(registerComponent:)])
		etDoc = newDoc; // Always reset if possible; bugfix for shared images, 12/23
	return self;
}
- touch
	{isDirty=YES; return self;}

//////////// LIFECYCLE MANAGEMENT ////////////
- init		// default initializer
{
	return [self initInDoc:nil linked:NO];
}

- initInDoc:newDoc linked: (BOOL) linked
{
	char tmp[128];
	
	[super init];
	isLinked = linked;
	etDoc = newDoc;
	isDirty = NO;
	shouldEdit = YES;
	icon = nil;
	sprintf(tmp, "Untitled-%d", [NXApp uniqueID]);
	componentName = NXUniqueString(tmp);
	currentPath = NXUniqueString("");
	return self;
}

////////////// READING/WRITING ///////////////
- readComponentFromPath:(const char*)newPath
{
	char *tmp;

	// Have we been passed a valid pathname?
	if(!(newPath  && (*newPath))) {
		NXLogError("Severe Error, bad path to readComponentFromPath");
		return nil;
	}
	if (access(newPath, F_OK|R_OK)) {
		NXLogError("%s could not access(%s)", [[self class]name],newPath);
	}
	currentPath = NXUniqueString(newPath);
	tmp = rindex(newPath,'/');
	if (tmp && *(tmp+1))
		componentName = NXUniqueString(tmp+1);
	return self;
}
- writeComponentToPath:(NXAtom)path inFormat:(int) theFormat
{
	char target[MAXPATHLEN];
	char cmd[2*MAXPATHLEN];
	
	switch(theFormat) {
		case ETFD_FMT:
			[etDoc registerComponent:componentName];	// always register
			// if it's linked, just return.
			if (isLinked) break;
			sprintf(target,"%s/%s",path,componentName);
			// see if the diskfile already exists
			if ((!isDirty) && (!access(target, F_OK|R_OK))) break;
			// ok, copy the raw bits.
			if (currentPath && *currentPath && target && *target) {
				sprintf(cmd,"cp -rp \"%s\" \"%s\"", currentPath, target);
				system(cmd);
			}
			// since we have saved ETFD, reset the "clean" state
			currentPath = NXUniqueString(target);
			isDirty = NO;
			break;
		case TeXD_FMT: if (isLinked) break;
		case HTMD_FMT:
			// In these formats, we always force copying of the data resource
			[etDoc registerComponent:componentName];	// always register
			sprintf(target,"%s/%s",path,componentName);
			// see if the diskfile already exists
			if ((!isDirty) && (!access(target, F_OK|R_OK))) break;
			if (currentPath && *currentPath && target && *target) {
				sprintf(cmd,"cp -rp \"%s\" \"%s\"", currentPath, target);
				system(cmd);
			}
			break;
	}
	return self;
}

- linkComponentToPath:(NXAtom)path
{
	char target[MAXPATHLEN];
	char cmd[2*MAXPATHLEN];

	[etDoc registerComponent:componentName];	// always register
	sprintf(target,"%s/%s",path,componentName);
	// see if the diskfile already exists
	if (access(target, F_OK|R_OK)){
		if (currentPath && *currentPath && target && *target) {
			sprintf(cmd,"ln -s \"%s\" \"%s\"", currentPath, target);
			system(cmd);
		}
	}
	return self;
}
- readComponentFromPboard:(Pasteboard *)thePboard
{
	NXAtom foundType;
	NXAtom supportedTypes[2] = {NXFilenamePboardType,
								NXFileContentsPboardType};

	foundType = [thePboard findAvailableTypeFrom:supportedTypes num:2];

	if (foundType == NXFilenamePboardType) {
		// can we derive a resource name? if not, make path the resource name
		char	*path;
		int		len;

		[thePboard readType:NXFilenamePboardType data:&path length:&len];
		// we only process _ONE_ filename; this defeats the tab-separation
		if (index(path, '\t')) *index(path, '\t')=0;
		if (rindex(path, '/')) {
			componentName = NXUniqueString(rindex(path,'/')+1);
			currentPath = NXUniqueString(path);
		} else {
			currentPath = componentName = NXUniqueString(path);
		}
		[thePboard deallocatePasteboardData:path length:len];
	} else if (foundType == NXFileContentsPboardType) {
		char *path,tmp[MAXPATHLEN];
		
		sprintf(tmp,"/tmp/eTComponent.in.%x", [NXApp uniqueID]);
		path = [thePboard readFileContentsType:NULL toFile:tmp];
		currentPath = NXUniqueString(path);
		componentName = NXUniqueString(rindex(path,'/')+1);
		free(path);	// readFile... returns a malloc'd actual path
	}
	return self;
}
- writeComponentToPboard:(Pasteboard *)thePboard
{
	// find out where the file is and encode that to the Pboard.
	if (!(*currentPath) || access(currentPath, F_OK)) return nil;
	[thePboard 	declareTypes:&NXFilenamePboardType num:1 owner:nil];
	[thePboard 	writeType:NXFilenamePboardType 
				data:currentPath length:strlen(currentPath)];
	return self;
}
- addToPboard:thePboard
{
	if (!(*currentPath) || access(currentPath, F_OK)) return nil;
	[thePboard 	addTypes:&NXFilenamePboardType num:1 owner:nil];
	[thePboard 	writeType:NXFilenamePboardType 
				data:currentPath length:strlen(currentPath)];
	return self;
}
///////// ADDITIONAL FMT PROTOCOL SUPPORT ////////////

- readRichText:(NXStream *)stream forView:view
{	
	char 	buf[MAXPATHLEN];
	const char *tmp;
	int		i;
	id temp;
	
	temp = [view etDoc];
	if (temp && [temp respondsTo:@selector(registerComponent:)])
		etDoc = temp; // Always reset if possible; bugfix for shared images, 12/23
	NXScanf(stream, "%d ", &i);
	if (i != _eTComponentVERSION) {
		// bad version block.
		NXLogError("eTComponent found unparseable version %d at position %d",
					i, NXTell(stream));
		return nil;
	}
	NXScanf(stream, "%c %c %d", &isLinked, &shouldEdit, &i); NXGetc(stream); // space-eater
	isLinked -= 'A'; shouldEdit -= 'A';
	if (i) NXRead(stream, buf, i);
	buf[i] = 0;
	currentPath = NXUniqueString(buf);
	
	NXScanf(stream, " %d", &i); NXGetc(stream); // space-eater
	if (i) NXRead(stream, buf, i);
	buf[i] = 0;
	componentName = NXUniqueString(buf);
	
	if(!access(currentPath, F_OK|R_OK) || isLinked) return self;
	if(etDoc && (tmp = [[etDoc docInfo] docPath])) {
		sprintf(buf,"%s/%s",tmp,componentName);
		if(!access(buf, F_OK|R_OK)) {
			currentPath = NXUniqueString(buf);
#ifdef DEBUG
			NXLogError("Found %s at %s not %s/%s", componentName,buf,currentPath,componentName);
#endif
			return self;
		}
	}
#ifdef DEBUG
	NXLogError("Could not access %s or %s! %s readRichText: failed!",
				 currentPath,buf,[[self class] name]);
#endif
	return nil;
}

- writeRichText:(NXStream *)stream forView:view
{
	// since this method is called before any writeComponents, we attempt
	// to precompute the future currentPath
	// RULE: if (isLinked), currentPath is valid
	// RULE: otherwise, componentName is valid, so precompute currentPath
	
	const char *tmp;
	char target[MAXPATHLEN];
	id temp;
	
	temp = [view etDoc];
	if (temp && [temp respondsTo:@selector(registerComponent:)])
		etDoc = temp; // Always reset if possible; bugfix for shared images, 12/23
	if (isLinked) 
		tmp = currentPath;
	else {
		sprintf(target, "%s/%s",[[etDoc docInfo] docPath], componentName);
		tmp = target;
	}
	NXPrintf(stream, "%d %c %c %d %s %d %s", 
		_eTComponentVERSION, isLinked+'A', shouldEdit+'A',
		strlen(tmp), tmp, strlen(componentName), componentName);
	return self;
}

- writeASCIIRef:(NXStream *)stream forView:view
{
	id temp;
	
	temp = [view etDoc];
	if (temp && [temp respondsTo:@selector(registerComponent:)])
		etDoc = temp; // Always reset if possible; bugfix for shared images, 12/23
	NXPrintf(stream, "A %s%s reference to the file %s\n", (isLinked ? "linked " : ""), [[self class] name], (isLinked ? currentPath : componentName));
	return self;
}

- writeHTML:(NXStream *)stream forView:view
	{return [self writeHTML:stream forView:view andClose:YES];}
- writeHTML:(NXStream *)stream forView:view andClose:(BOOL)closeIt
{
	id temp;
	
	temp = [view etDoc];
	if (temp && [temp respondsTo:@selector(registerComponent:)])
		etDoc = temp; // Always reset if possible; bugfix for shared images, 12/23
	//NXPrintf(stream, "<A HREF=\"%V%V\">%v:%v</A>", 
	//		(isLinked ? "http://localhost/" : ""),
	//		(isLinked ? currentPath : componentName), 
	//		[[self class] name],(isLinked ? currentPath : componentName));
	// In the NEW scheme of things, HTML component data is _ALWAYS_
	//	written out explicitly, so none of this localhost shit.
	NXPrintf(stream,"<A HREF=\"%V\">",componentName);
	if (closeIt)
		NXPrintf(stream,"%v</A>",componentName);
	return self;
}

- writeLaTeX:(NXStream *)stream forView:view
	{return [self writeLaTeX:stream forView:view andClose:YES];}
- writeLaTeX:(NXStream *)stream forView:view andClose:(BOOL)closeIt
{
	id temp;
	
	temp = [view etDoc];
	if (temp && [temp respondsTo:@selector(registerComponent:)])
		etDoc = temp; // Always reset if possible; bugfix for shared images, 12/23
	NXPrintf(stream, "\n\\footnote{A %w%w reference to the file %w.", (isLinked ? "linked " : ""), [[self class] name], (isLinked ? currentPath : componentName));
	if (closeIt) 
		NXPrintf(stream, "}\n");
	return self;
}
@end